/*! \file 
	\brief Holds the implementation of the memory sub-system. */

#include "kernel.h"
#include "mm.h"

/* Variable definitions. */

unsigned long currently_allocated_mem = 0;

struct page_frame page_frame_table[MAX_NUMBER_OF_FRAMES];

volatile unsigned int page_frame_table_lock = 0;

/* Set when the system is initialized. */
unsigned long memory_pages;

/* The following three variables are set by the assembly code. */
unsigned long first_available_memory_byte;

const unsigned long memory_size;

const unsigned long kernel_page_table_root;

/* Function definitions. */

/* Change this function in assignment 4. */
extern long
kalloc(const register unsigned long length,
		const register unsigned int process,
		const register unsigned long flags)
{
	MENSURE(length > 0, "Can not allocate zero or negative amount of memory",{return ERROR;});

	LOCK4WRITE(&page_frame_table_lock);
	int requested_pages = (length + 4095) >> 12; // round up to integer page number
	const int max_page = memory_pages - 1; // memory_pages is a count, make it 0-based
	int i;

	int cnt = 0;
	int page_num; // Page number of first page in the allocated block of pages

	/* Similar to first fit (just on bitmaps) [Tanenbaum p.184].
	 * Starting from top mem, going down until first fit */
	for (page_num = max_page; page_num >= 0; page_num--) {
		if (page_frame_table[page_num].owner == -1) {
			cnt++;
		} else {
			cnt = 0;
		}

		if (cnt == requested_pages) {
			goto found;
		}
	}

	/* We did not find the requested number of pages. */
	RELEASE_LOCK(&page_frame_table_lock);
	return ERROR;


found:
	/* We did find the requested number of pages. */

	for (i = 0; i < requested_pages; i++) {
		page_frame_table[page_num + i].owner = process;
		page_frame_table[page_num + i].start = page_num;
		page_frame_table[page_num + i].free_is_allowed = !(flags & ALLOCATE_FLAG_KERNEL);
	}

	currently_allocated_mem += requested_pages; // We've succesfully allocated the requested # of pages

	report_usage(); // only call if we actually succeded in allocating memory
	RELEASE_LOCK(&page_frame_table_lock);
	return page_num << 12; // Return byte-address for the found page
}

long
kfree(const register unsigned long address)
{
	LOCK4WRITE(&page_frame_table_lock);

	typedef int bool; // C has no bool built-in. bools convey semantics better than int's
	const int page_num = address >> 12; // 'address' is byte-addressed; we need the page number
	const int pid = kgetpid();

	/* Get the page which contains 'address' */
	struct page_frame* page = &page_frame_table[page_num];

	const bool is_owner = (pid == page->owner); // Must not allow any procsess to free any other proceses' memory
	const bool is_start = (page->start == page_num); // 'address' must point to beginning of block (though not needed in bitmapped implementation)
	const bool okay = (is_owner && is_start && page->free_is_allowed); // Obey the free_is_allowed flag

	if (!okay) {
		RELEASE_LOCK(&page_frame_table_lock);
		return ERROR;
	}

	/* We are okay now, proceed to free all pages in allocated block */
	while (page->start == page_num) // Free only pages belonging to this block (ie. all those pages saying belong to the block starting at page page_num)
	{
		page->owner = -1; // Nobody owns the page now
		page->start = -1; // It does not belong to any memory block
		page->free_is_allowed = 0; // And don't allow double free()ing
		page++; // goto next page 

		currently_allocated_mem -= 1; // We've free'd 1 page
	}

	report_usage();
	RELEASE_LOCK(&page_frame_table_lock);
	return ALL_OK;
}

unsigned long 
kget_mem_usage_in_bytes() 
{
	return currently_allocated_mem * (4*1024);
}

unsigned long 
kget_mem_usage_in_pages() 
{
	return currently_allocated_mem;
}

void report_usage() {
	static int cnt = 0;
	if ((cnt++ % 200) == 0) {
		kprints_bochs("Mem usage: \0");
		kprinthex_bochs( kget_mem_usage_in_pages() );
		kprints_bochs(" pages.\n\0");
	}
}

/* Implementing this fuction is not part of assignment 4. */
extern void
update_memory_protection(const register unsigned long page_table,
		const register unsigned long start_address,
		const register unsigned long length,
		const register unsigned long flags)
{
}

/* Implementing this fuction is not part of assignment 4. */
extern void
initialize_memory_protection()
{
}

/* Put any code you need to add here. */
